'''
NurbsCad
Archivo plano utilizado principalmente por el archivo escena.py 
Autor: Gustavo Enrique Bellino
Contacto: gushbellino@gmail.com
Fecha: Junio de 2009
Descripcion: archivo con una clase llamada Plano utilizada para dibujar y manipular un plano.
Licencia: GNU Public License version 2
'''

from __future__ import division
import os, sys, math

#Se agrega al path la carpeta ../
modulesdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
sys.path.insert(0, modulesdir)

#librerias de un nivel superior
from library import *

class Plano(Library):
    ''' Clase que manipula un plano '''
    def __init__(self, identifier, escena, p1 = None, p2 = None, p3 = None, tamanio = 2, name = None):
        """"
        Initializes the class Plano
        @param identifier: Identify the object to the escene.
        @type identifier: integer.
        @param p1: First point of the plane.
        @type p1: Vector3 or None by default.
        @param p2: Second point of the plane.
        @type p2: Vector3 or None by default.
        @param p3: Third point of the plane.
        @type p3: Vector3 or None by default.
        @param tamanio: The size of the plane.
        @type tamanio: integer.
        @param name: Name of the plane.
        @type name: string.
        @return: None if the points are colineals.     
        """
        self.r = 0
        #para actualizar la pantalla
        self.escena = escena
        
        #Numero unico que representa al objeto, este es asignado por la escena.
        self.identifier = identifier
        
        #Permite que escena elimine el objeto
        self.autoEliminate = False
        
        self.type = 'Plano'
        if(name==None):
            self.name = 'Plano 01'
        else:
            self.name = name
        
        #Colores de los elementos del plano.
        self.colorToDrawPlane = [0.6, 0.78, 1.0, 1.0]
        self.colorToDrawCircle = [0.0, 0.0, 0.0, 0.0]
        self.colorSelectedPlane = [0.48, 0.64, 0.85, 0.0] #cuando el plano esta seleccionado
        self.colorToDrawCone = [1, 0.0, 0.0, 0]
        self.colorSphere = (1, 0, 0, 0)
        #Estado seleccionado, inicializado en False.
        self.bSelected = False
        
        #estado para mover el plano
        self.movingPlane = False
        
        #tamanio del plano a dibujar
        self.tam = tamanio
        if(isinstance(p1, type(None)) and 
                               (isinstance(p2, type(None)))and 
                               (isinstance(p3, type(None)))):
            #inicializa todo en el origen
            self.eye = Vector3(0, 0, 1)
            self.lookAt = Vector3(0, 0, 0)
            self.up = Vector3(0, 1, 0)
            self.origen = Vector3(0, 0, 0)
            self.bSelected = False
            return
        elif(isinstance(p1, Vector3) and (isinstance(p2, Vector3))and (isinstance(p3, Vector3))):
            normal = self.makeNormal(p1, p2, p3)
            if(normal):
                self.eye = normal[0]
                self.up = (p1-p2).normalized()
                self.lookAt = Vector3(0, 0, 0)
                d = normal[1]
                self.origen = p1
            else:
                return None

    def getClassName():
        return 'Plano'
    getClassName = staticmethod(getClassName)

    def desplazarZ(self, vectDesp):
        """
        Moves the origin of the plane. The new origin is vectDesp + origin.
        @param vectDesp: displacement vector.
        @type vectDesp: Vector3.
        @return: None.
        """
        self.origen += vectDesp 
        #imprime el punto
        self.escena.set_info(self.printVector3(self.origen))
        return

    def desplazar(self, newOrigen):
        """
        Set the origin of the plane.
        @param newOrigen: new origin.
        @type newOrigen: Vector3.
        @return: None.
        """
        self.origen = newOrigen 
        return

    def getNormal(self):
        return  self.eye
    
    def getOrigen(self, v3ToAdd=Vector3(0, 0, 0)):
        self.origen += v3ToAdd
        return self.origen

    def getAcimut(self):
        return self.acimut

    def getLatitud(self):
        return self.latitud

    def makeNormal(self, p1, p2, p3, tol=0.0000000001):
        '''Retorna un vector con la normal de los tres puntos pasados por parametros que forman un plano.
        Estos puntos no deben ser colineales. En dicho caso la funcion retorna None'''
        #obtengo la normal del plano
        p1_p2 = p2 - p1
        p1_p3 = p3 - p1
        angulo = p1_p2.angle(p1_p3, 'radian')
        if(angulo):
            #si se aproxima mucho a una recta no es posible formar la normal
            if(angulo<tol or abs(angulo-math.pi)<tol):
                return None
            else:
                N = p1_p2.cross(p1_p3)
                N.normalize()
                d = p1.dot(N)
                return [N, d]
        else:
            return None
    
    def interseccion(self, recta):
        ''' Retorna la interseccion entre una recta y el plano.
        Params: recta, lista con los dos puntos que forman la recta: [p1,p2]
        '''
        retorno = None
        #calcula la distancia de los puntos al plano
        p1 = recta[0]
        p2 = recta[1]
        #se calcula la proyeccion del vector formado entre el punto y el origen de la normal sobre el vector de la normal
        #proy(origen->p1 / N)
        #punto 1
        origen = self.origen
        normal = self.eye
        proy1 = ((p1-origen).proyection(normal))

        #punto 2
        proy2 = ((p2-origen).proyection(normal))
        
        #si ambos son cero la recta esta sobre el plano
        if(proy1==proy2==0):
            retorno = None
        elif(proy1!=0 and proy2!= 0):#los puntos de la recta no estan sobre el plano
            #evaluamos si la recta es paralalela al plano
            v = p1-p2#vector direccion
            denom = normal.dot(v)
            if(denom!=0): #la recta no es paralela al plano y existe una interseccion
                #valor de desplazamiento del plano
                d = origen.proyection(normal)
                numerador = d - normal.dot(p1)
                t = (d - normal.dot(p1)) / denom
                interseccion = p1 + t*v
                retorno = interseccion
            else:
                retorno = None #la recta es paralela al plano
        elif(proy1==0 and proy2!= 0):#el punto de interseccion es p1
            retorno = p1
        elif(proy1!=0 and proy2== 0):#el punto de interseccion es p2
            retorno = p2
        return retorno

    def proyOrt(self, p):
        '''Retorna un punto Vector3 que indica la proyeccion ortogonal del punto p
        params: p Vector3
        return: Vector3
        '''
        v = p - self.getNormal() #vector direccion
        recta = [p, v]
        proy = self.interseccion(recta)
        return proy
    
    def setPlaneColor(self, color=(1.0, 1.0, 1.0, 0.5)):
        self.colorToDrawPlane = color

    def setPlaneSelectedColor(self, color=(1.0, 1.0, 1.0, 0.5)):
        self.colorSelectedPlane = color

    def setCircleColor(self, color=(1.0, 1.0, 1.0, 0.5)):
        self.colorToDrawCircle = color
    
    def draw(self, selectMode=False, normalLength = 1):
#se setean los colores
        
        glPushMatrix() #1
        desp = self.origen
        glTranslated(desp.x, desp.y, desp.z)
        glPopMatrix()
        
        if(not selectMode):
            #modo de renderizado normal
            colorPlano = self.colorToDrawPlane
            colorCirculo = self.colorToDrawCircle
            colorNormal = self.colorToDrawCircle
            colorCone = self.colorToDrawCone
            colorSphere = self.colorSphere
            if(self.bSelected):
                #admeas de no estar en modo seleccion esta seleccionado
                colorPlano = self.colorSelectedPlane
        else:
            #modo de seleccion, identifier representa a un color
            inc = (1/255)
            red = (inc) * self.identifier
            color = [red, red, red,  0]
            colorPlano = color
            colorCirculo = color
            colorNormal = color
            colorCone = color
            colorSphere = color
        
        glPushMatrix() #1
        desp = self.origen
        glTranslated(desp.x, desp.y, desp.z)
        
        #rectangulo
        
        
        rigth = self.up.cross(self.eye)
        a = self.up + rigth
        b = self.up - rigth
        tam = 0.8
        punto0 = a * tam
        punto1 = b * tam
        punto2 = -punto0
        punto3 = -punto1
        self.dPlanePoints = True
        
        factor = 0.8
        uf = self.up * factor
        rf = rigth * factor
        a = punto0 - uf
        b = punto1 -  uf
        c = punto2 + uf
        d = punto3 +  uf
        e = punto0 - rf
        f = punto1 + rf
        g = punto2 + rf
        h = punto3 - rf
        
        self.draw_rectangule(a, punto0, punto1, b , colorPlano, True)
        self.draw_rectangule(c, punto2, punto3, d , colorPlano, True)
        self.draw_rectangule(punto0, e, h, punto3 , colorPlano, True)
        self.draw_rectangule(f, punto1, punto2, g , colorPlano, True)
        
#        self.draw_rectangule(punto0, punto1, punto2, punto3, colorPlano, True)
        
        self.draw_line(self.lookAt, self.up * 2, color = (1, 0, 0, 0))
        self.draw_line(self.lookAt, rigth * 2, color = (0, 1, 0, 0))
        self.draw_line(self.lookAt, self.eye * 2, color = (0, 0, 1, 0))
        
        #Eje de coordenadas del plano
#        origen = self.eye
#        radius = 0.1
#        slices = 10
#        stacks = 10
#        color = colorSphere
#        self.draw_sphere(origen,  radius, slices, stacks, color)
        glPopMatrix() #1

   #
    def interEsfera(self, p0, p1, c, r):
        ''' Retorna los puntos que interceptan la esfera, sino lo la intercepta retorna falso '''
        v = (p1 - p0)
        v.normalize()
        cP = (c-p0).dot(v) #c paralelo
        cP = cP * v
        cT = (c-p0)-cP

        modulo = cT.module()
        if(modulo<=r):
            R1 = math.sqrt(r**2 - modulo**2)
            p_back = p0 + cP + v*R1
            p_front = p0 + cP - v*R1
            return [p_front, p_back]
        else:
            return [-cT + c]
            

#CallBack Mouse
    def Mouse(self, x, y, camara, color):
        if(not self.movingPlane):
            #se identifica el radio de la esfera
            pClicked = self.convertTo3D(x, y) #click en la pantalla
            recta = [camara.eyes, pClicked]
            self.v0 = self.interseccion(recta) #punto en el espacio proyectado en el plano
            self.r = (self.v0 - self.origen).module()
            self.front = (camara.eyes-self.origen).module() >= (camara.eyes-self.v0).module()
            self.changed = False
        else:
            self.movingPlane = False
        return 0

#CallBack OnMotion
    def Motion(self, x, y, camara, speed=1):
        ''' Movimiento del plano cuando esta seleccionado'''
        if(self.bSelected):
            if(not self.movingPlane):
                pClicked = self.convertTo3D(x, y) #click en la pantalla
                p0 = camara.eyes
                p1 = pClicked
                c = self.origen
                r = self.r
                inter =  self.interEsfera(p0, p1, c, r) #el rayo subido a la esfera
                self.i = inter
                interceptaEsfera = inter.__len__()==2
                
                if(interceptaEsfera): #intercepta a la esfera
                    self.changed = False #cambiado el front
                    if(self.front): # si esta en el frente o atras
                        v1 = inter[0]
                    else:
                        v1 = inter[1]
                else: #esta fuera de la esfera
                    v1 = inter[0] #el costado de la esfera
                    if(not self.changed):
                        self.front = not self.front
                        self.changed = True
                    
                eje = (v1-self.origen).cross(self.v0-self.origen)
                self.eje = eje
                ang = (v1-self.origen).angle(self.v0-self.origen)
                self.eye = self.eye.rotate(-ang, eje)
                self.up = self.up.rotate(-ang, eje)
                #actualiza el punto
                self.v0 = v1                        
                return 0
            elif(self.movingPlane):
                #se identifica el radio de la esfera
                pClicked = self.convertTo3D(x, y) #click en la pantalla
                recta = [camara.eyes, pClicked]
                proyeccion = self.interseccion(recta) #punto en el espacio proyectado en el plano
                self.desplazar(proyeccion)

    def Scroll(self, direction):
        vect = self.eye.normalize()/10
        if(direction):
            self.desplazarZ(vect)
        else:
            self.desplazarZ(-vect)
        return

    def atributesToEdit(self):
        self.atributes = {'Origen': (self.getOrigen(), lambda widget,spinbutton_widget1,spinbutton_widget2,spinbutton_widget3:  self.setOrigen(widget,spinbutton_widget1,spinbutton_widget2,spinbutton_widget3))
                                    }
        return self.atributes
    
    def setOrigen(self,widget, spinbutton_widget1,spinbutton_widget2,spinbutton_widget3):
        valorX = spinbutton_widget1.get_value()
        valorY = spinbutton_widget2.get_value()
        valorZ = spinbutton_widget3.get_value()
        valorV=Vector3(valorX,valorY,valorZ);
        self.origen = valorV
        self.escena.queue_draw()
        return True
    
    def actionsToDo(self):
        self.actions = {
            '02-plano/plano_x_y.png': (lambda x: self.setPlano_x_y(), 'Posicionar: plano x-y'), 
            '03-plano/plano_z_x.png': (lambda x: self.setPlano_x_z(),'Posicionar: plano x-y'), 
            '04-plano/plano_y_z.png': (lambda x: self.setPlano_y_z(), 'Posicionar: plano x-y')
                            }
        return self.actions.keys()
    
    def setPlano_x_y(self):
        self.eye = Vector3(0, 0, 1)
        self.up = Vector3(0, 1, 0)
        self.escena.queue_draw()
   
    def setPlano_x_z(self):
        self.eye = Vector3(0, 1, 0)
        self.up = Vector3(0, 0, 1)
        self.escena.queue_draw()

    def setPlano_y_z(self):
        self.eye = Vector3(1, 0, 0)
        self.up = Vector3(0, 1, 0)
        self.escena.queue_draw()

    def activeMovingPlane(self):
        self.movingPlane = True
    
    def functionReturn(self, widget, event):
        indice = widget.get_label()
        return self.actions[indice]()

    def functionReturnToEdit(self, indice, value):
        return self.atributes[indice][1](value)

